在 TypeScript 中,最重要的就是 Interfaces 和 Class 的協作,Interfaces 和 Class 的相互作用,是我們在編寫 TypeScript 時真正獲得重用性的原因 -b
今日大綱:
今天會先從為甚麼要用 Interfaces 開始舉例說起
然後再舉例說明一些比較正確使用 Interfaces 的情境
Interfaces 翻成介面 or 接口,他其實跟 number, string, boolean 一樣,就是一種型別(Type),只是他是用來描述物件的 屬性名稱
和 值的類型
。
今天就來記錄一下,甚麼是 Interfaces
const monster = {
name: 'godzilla',
year: 1954,
canFly: true,
}
const printMonster = (monster: {name: string, year: number, canFly: boolean}) => {
console.log(`
name = ${monster.name}
born in ${monster.year}
can fly? ${monster.canFly}
`)
}
雖然程式碼沒有問題,但是 printMonster
的接收的參數 monster
的 Type Annotations 真的太長了,導致程式碼難以閱讀
而且如果現在有其他方法也要接收一樣的參數,那該方法也要在寫一次一樣的 Type Annotations,簡直是場災難
const monster = {
name: 'godzilla',
year: 1954,
canFly: true,
}
const printMonster = (monster: {name: string, year: number, canFly: boolean}) => {
console.log(`
name = ${monster.name}
born in ${monster.year}
can fly? ${monster.canFly}
`)
}
const printMonsterStory = (monster: {name: string, year: number, canFly: boolean}) => {
console.log(`It's name is = ${monster.name}`)
}
這時我們就可以使用更好的解決方法 => Interfaces
建立一個 Interfaces,我們可以想像就像建立一個新型別,就像 number, string 那樣的型別
TypeScript 會像檢查其他型別一樣,檢查我們定義好的這個值有沒有符合 interface
interface Monster {
name: string;
year: number;
canFly: boolean;
}
const monsterGodzilla: Monster = {
name: 'godzilla',
year: 1954,
canFly: true,
}
像這樣我們定義好一個 interface 接口,並在宣告物件 monsterGodzilla
時註記要符合 interface Monster
,這樣子 TypeScript 就會檢查 monsterGodzilla
是否符合 interface Monster
確認裡面的 屬性名稱
和 值的類型
是否正確,如果有錯了就會報錯
Interface 中定義屬性的 Type,不僅僅只侷限在一般基本的 Type ,所以的 Type 都可以,像是 Date 跟 function 都可以
interface Monster {
name: string;
year: Date;
canFly: boolean;
attack(): string;
}
但是在使用 interface 的時候也要注意,有時候有些錯誤不會在程式馬上出現,而是語意上的錯誤,這類錯誤容易造成未來 Type 上有錯誤
來看以下程式碼
// 怪獸的 interface
interface Monster {
name: string;
year: number;
canFly: boolean;
attack(): string;
}
// 現在有的一個怪獸物件
const godMonster = {
name: 'godzilla',
year: 1954,
canFly: true,
attack(): string {
return `${this.name} attack!!!`
}
}
// - 現在有的其一方法,傳入參數 Type 的定義為 Monster
// - 語意為: 要符合怪獸條件的才可傳入,TypeScript 會檢查
// - printMonsterAttack 命名的語意為印出怪獸
const printMonsterAttack = (monster: Monster) => {
console.log(monster.attack())
}
printMonsterAttack(godMonster)
這段看起來沒問題的程式碼其實藏有一些語意上的錯誤、Type 的錯誤,以及可以優化的地方
下面就來看看如何優化
printMonsterAttack
命名的語意為印出怪獸攻擊,且接收參數須符合怪獸的 interface但是這個方法只有用到 Monster
interface 的 attack()
方法,這樣子我們還可以說,一個怪獸一定要符合 Monster
interface 的所有特性嗎?
答案是 NO ,可以看下面這段,如果我們把 interface 上的一些特性刪掉,並不會跳任何錯誤
為甚麼呢?
我們這個寫法,當我們調用 printMonsterAttack()
的時候,傳入的參數需要符合 Monster
interface,我們傳入 godMonster
,TypeScript 在背後幫我們做了一次快速檢查,這個 godMonster
是否符合 Monster
interface
檢查內容就是檢查
godMonster
裡面是否有一個attack()
參數,並且是回傳 string 的。
實際檢查godMonster
內部發現 OK 這裡面確實有attack()
,並且回傳的也是 string。
然後... 就沒有然後了
這是 TypeScript 唯一會檢查的問題。
其他 godMonster
裡面有其他參數,TS 都不會在意
不會說,哦哦 你有其他參數,你不能稱為怪獸
他只會去檢查 interface 的中的屬性列表是否符合
因為 interface 裡面只有檢查是否有 attack(): string
參數,所以 interface 的名稱改成這樣更加合理 Attackable
這樣寫的話,就可以說,要被認為是可攻擊類型 (Attackable Type) 的話,你必須要有一個名稱為 attack()
的參數,並且返回值要是 string。
現在這樣寫,對 interface Attackable
的定義精簡了很多,但是通用性更高了
printMonsterAttack() 的取名現在看起來,跟怪獸有什麼關係呢?
他只是印出怪獸的攻擊
這個函式現在做的事情是檢查參數有可以 attack()
的方法,然後印出攻擊名稱,所以其實跟怪獸沒有關係,我們的語意也可以改變,函式的名稱和參數名稱都可以修改一下
這樣就調整完一些語意不合的部分,之後會再提到為甚麼需要這樣『正確』的語意呢?
現在我們來試著加入完全不同的物件 player
interface Attackable {
attack(): string;
}
const godMonster = { // 怪獸物件
name: 'godzilla',
year: 1954,
canFly: true,
attack(): string {
return `${this.name} attack!!!`
}
}
const player1 = { // 新加的不同物件
name: 'Victor',
hp: 100,
skill: 'fire ball',
attack(): string {
return `${this.name} use ${this.skill}!!`
}
}
const printAttack = (target: Attackable) => {
console.log(target.attack())
}
printAttack(godMonster)
printAttack(player1) // 可以正確被傳入 printAttack()
看出來了嗎?這就是 interface 的意義
這兩個代表非常不同的物件,但是他們都有一個共同的方法 printAttack()
,這代表他們都被視為符合 Attackable
Type,所以這兩個物件都可以被傳入 printAttack()
中使用
上面這個重點是,我們可以使用同一個 Interface 來描述非常不同的兩個物件
通過這樣實作,我們可以讓這些不同的物件共用一個 Interface、使用同一個方法 printAttack()
,並且符合語意
我們現在在應用程序中獲得了一個可重用性更高的函式 printAttack
const printAttack = (target: Attackable) => {
console.log(target.attack())
}
這個方法可以滿足任何滿足 type 符合 Attackable
的物件
這個範例雖然十分做作,但是可以初步了解 Interface 在 typescript 中的意義。